﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using Inspire.DataStructures;

namespace Inspire.UIResources
{
    /// <summary>
    /// Interaction logic for NumberBox.xaml
    /// </summary>
    public partial class NumberBox : UserControl, INotifyPropertyChanged
    {
        /// <summary>
        /// A number box with increment and decrement arrow buttons.
        /// </summary>
        public NumberBox()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Value DependencyProperty of the control.
        /// </summary>
        public static readonly DependencyProperty ValueProperty = 
            DependencyProperty.Register(
                "Value",
                typeof(int), 
                typeof(NumberBox),
                new PropertyMetadata(0, new PropertyChangedCallback(OnValuePropertyChanged))
            );

        /// <summary>
        /// Event handler for when the DependencyProperty changes.
        /// </summary>
        /// <param name="target"></param>
        /// <param name="e"></param>
        private static void OnValuePropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            NumberBox numberBox = target as NumberBox;
            numberBox.TextValue.Text = e.NewValue.ToString();
        }

        /// <summary>
        /// Value of the Control
        /// </summary>
        private int _value = 0;
        public int Value
        {
            get
            {
                return this._value;
            }
            set
            {
                // This prevents WPF from invalidating my Value DependencyProperty when the template is switched
                int outPlace = 0;
                if ((Int32.TryParse(this.TextValue.Text, out outPlace) && value == Int32.Parse(this.TextValue.Text)))
                {
                    this._value = value;
                    SetValue(ValueProperty, this._value);
                    this.NotifyPropertyChanged("Value");
                }
                else
                {
                    Console.Out.WriteLine("Value Change ATTEMPT: " + value.ToString());
                }
            }
        }

        /// <summary>
        /// Maximum DependencyProperty of the control.
        /// </summary>
        public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(int), typeof(NumberBox));

        /// <summary>
        /// Maximum property of the control.
        /// </summary>
        public int Maximum
        {
            get
            {
                return (int)GetValue(MaximumProperty);
            }
            set
            {
                SetValue(MaximumProperty, value);
            }
        }

        /// <summary>
        /// Maximum DependencyProperty of the control.
        /// </summary>
        public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(int), typeof(NumberBox));

        /// <summary>
        /// Maximum property of the control.
        /// </summary>
        public int Minimum
        {
            get
            {
                return (int)GetValue(MinimumProperty);
            }
            set
            {
                SetValue(MinimumProperty, value);
            }
        }

        /// <summary>
        /// Class encapsulating a RoutedEvent for the Value changing.
        /// </summary>
        private static readonly RoutedEvent ValueChangedEvent =
            EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NumberBox));

        /// <summary>
        /// Event for the Value changing. Other objects can subscribe to this and be notifie by the RoutedEvent.
        /// </summary>
        public event RoutedEventHandler ValueChanged
        {
            add
            {
                this.AddHandler(ValueChangedEvent, value);
            }
            remove
            {
                this.RemoveHandler(ValueChangedEvent, value);
            }
        }

        /// <summary>
        /// Class encapsulating a RoutedEvent for the Increase Button being clicked.
        /// </summary>
        private static readonly RoutedEvent IncreaseClickedEvent = 
            EventManager.RegisterRoutedEvent("IncreaseClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NumberBox));

        /// <summary>
        /// Event for the Increase Button being clicked. Other objects can subscribe to this and be notified by the RoutedEvent.
        /// </summary>
        public event RoutedEventHandler IncreaseClicked
        {
            add
            {
                this.AddHandler(IncreaseClickedEvent, value);
            }
            remove
            {
                RemoveHandler(IncreaseClickedEvent, value);
            }
        }

        /// <summary>
        /// Event Handler for the Increase button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Increase_Click(Object sender, RoutedEventArgs e)
        {
            if (this.Value < this.Maximum)
            {
                TextValue.Text = (this.Value + 1).ToString();
                this.RaiseEvent(new RoutedEventArgs(IncreaseClickedEvent));
            }
        }

        /// <summary>
        /// Class encapsulating a RoutedEvent for the Decrease Button being clicked.
        /// </summary>
        private static readonly RoutedEvent DecreaseClickedEvent =
            EventManager.RegisterRoutedEvent("DecreaseClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NumberBox));


        /// <summary>
        /// Event for the Decrease Button being clicked. Other objects can subscribe to this and be notified by the RoutedEvent.
        /// </summary>
        public event RoutedEventHandler DecreaseClicked
        {
            add
            {
                this.AddHandler(DecreaseClickedEvent, value);
            }
            remove
            {
                this.RemoveHandler(DecreaseClickedEvent, value);
            }
        }

        /// <summary>
        /// Event Handler for the Decrease button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Decrease_Click(Object sender, RoutedEventArgs e)
        {
            if (this.Value > this.Minimum)
            {
                TextValue.Text = (this.Value - 1).ToString();
                this.RaiseEvent(new RoutedEventArgs(DecreaseClickedEvent));
            }
        }
        
        /// <summary>
        /// Event Handler for the textbox's ValueChanged event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void value_TextChanged(object sender, TextChangedEventArgs e)
        {
            int inputValue = 0;

            if (Int32.TryParse(this.TextValue.Text, out inputValue))
            {
                // Impose Max and Min Limits only if the DependencyProperty's are initialized
                if ((int)this.GetValue(MaximumProperty) > 0)
                {
                    if (Int32.Parse(this.TextValue.Text) > Maximum)
                    {
                        this.TextValue.Text = this.Maximum.ToString();
                        return;
                    }
                    if (Int32.Parse(this.TextValue.Text) < Minimum)
                    {
                        this.TextValue.Text = this.Minimum.ToString();
                        return;
                    }
                }

                TextBox textBox = (TextBox)sender;
                try
                {
                    this.Value = Int32.Parse(this.TextValue.Text);
                }
                catch (FormatException ex)
                {
                    Console.Out.WriteLine("Error Parsing Text to Int32!\n" + ex.StackTrace);
                    ResetText(textBox);
                    return;
                }

                RaiseEvent(new RoutedEventArgs(ValueChangedEvent));
            }
        }

        /// <summary>
        /// Event Handler for the textbox's PreviewTextInput event. This is called when an attempt is made to add a new character.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void value_PreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            TextBox textBox = (TextBox)sender;
            String text = textBox.Text.Insert(textBox.CaretIndex, e.Text);

            int newValue = 0;
            
            // If it cannot be parsed, then set handled to TRUE to stop processing here
            e.Handled = !Int32.TryParse(text, out newValue);
        }

        /// <summary>
        /// Handle the Up and Down arrow keys.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void value_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.IsDown && e.Key == Key.Up && this.Value < this.Maximum)
            {
                this.Value++;
                RaiseEvent(new RoutedEventArgs(IncreaseClickedEvent));
            }
            if (e.IsDown && e.Key == Key.Down && this.Value > this.Minimum)
            {
                this.Value--;
                RaiseEvent(new RoutedEventArgs(DecreaseClickedEvent));
            }
        }

        /// <summary>
        /// Resets the TextBox value to either 0 or the Minimum, whichever is lesser.
        /// </summary>
        /// <param name="textBox"></param>
        private void ResetText(TextBox textBox)
        {
            if (0 < this.Minimum)
            {
                textBox.Text = this.Minimum.ToString();
            }
            else
            {
                textBox.Text = "0";
            }
            textBox.SelectAll();
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}
